{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d5d14b97",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash\n",
    "!(stat -t /usr/local/lib/*/dist-packages/google/colab > /dev/null 2>&1) && exit\n",
    "pip install git+https://github.com/davidbau/baukit > /dev/null"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5bf765e1",
   "metadata": {},
   "source": [
    "## Using baukit show()\n",
    "\n",
    "`show()` is similar to notebook's `display()`, but it is gives you more control over layout and rendering of the HTML.\n",
    "\n",
    "To start, you can use it just like `print()` or `display()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ac2488b8",
   "metadata": {},
   "outputs": [],
   "source": [
    "from baukit import show\n",
    "\n",
    "show('hello', 'world')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d344e964",
   "metadata": {},
   "source": [
    "A doubly nested array shows as a row."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f407e0d0",
   "metadata": {},
   "outputs": [],
   "source": [
    "show([[1,2,3,4,5]])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "30e52379",
   "metadata": {},
   "source": [
    "The first level of nesting stack vertically, so you can make a table by stacking rows."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3f744f5d",
   "metadata": {},
   "outputs": [],
   "source": [
    "show([\n",
    "      [  1,   2,   3,   4,   5],\n",
    "      ['a', 'b', 'c', 'd', 'e']\n",
    "     ])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8d40a6a9",
   "metadata": {},
   "source": [
    "## Easy layouts by nesting arrays\n",
    "\n",
    "`show()` makes it easy to produce layouts of data by arranging it in nested arrays.  At the top level, data is arranged in rows; then at the second level, data is arranged in columns; then at the third level, it is split into rows again, and so on.\n",
    "\n",
    "(Technical detail: the HTML it produces uses [CSS flexbox layouts](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox), alternating row and column flex containers for each level of nesting.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c67bd9a1",
   "metadata": {},
   "outputs": [],
   "source": [
    "show(['first row ' * 30,\n",
    "      ['second row ' * 10, 'second row second item ' * 5],\n",
    "      ['third row ' * 10, ['third row second item ' * 5, ['some more', 'data', 'here']]]])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b43c195d",
   "metadata": {},
   "source": [
    "## A simple grid layout example\n",
    "\n",
    "A very common pattern is to show data in a grid.  Here is a three-level nested piece of retangular data:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c969092b",
   "metadata": {},
   "outputs": [],
   "source": [
    "show([[['hello', [i, j]] for j in range(1, 8)] for i in range(1, 6)])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3c2ec531",
   "metadata": {},
   "source": [
    "## Showing images\n",
    "\n",
    "Unlike `display()`, `show()` knows how to directly render PIL images and matplotlib figures and other types of data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "623a727b",
   "metadata": {},
   "outputs": [],
   "source": [
    "from PIL import Image\n",
    "from numpy.random import randint\n",
    "\n",
    "show([[[f'image {i}',\n",
    "        Image.fromarray(randint(0,255,(128,128,3),dtype='uint8'))]\n",
    "       for i in range(10)]])\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "118175c2",
   "metadata": {},
   "source": [
    "## Controlling CSS\n",
    "\n",
    "The style of anything emitted by show can be controlled by using `show.style()` to inject CSS properties.  `show.style` will alter the CSS style of the next item rendered by `show()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0799c0f5",
   "metadata": {},
   "outputs": [],
   "source": [
    "show([show.style(background='pink', font='italic 24px serif'), 'A demonstration of CSS control',\n",
    "      [show.style(background='skyblue'), 2, show.style(background='yellow', flex=2), 3],\n",
    "      show.style(background='lightgreen'), 4])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d28f281e",
   "metadata": {},
   "source": [
    "In the following style we define a style object for our favorite style, so we can use it repeatedly."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ff350115",
   "metadata": {},
   "outputs": [],
   "source": [
    "italic = show.style(background='pink', font='italic 24px serif', border='1px solid black')\n",
    "\n",
    "show(['hello', [italic, 'world', italic, 'look at me!']])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b78f64fa",
   "metadata": {},
   "source": [
    "## Tight columns and wrapped rows\n",
    "\n",
    "A few useful `show.style` instances are predefined as constants.  For example, `show.style(display='inline-flex')` is also called `show.TIGHT`, because it provides a tight layout of rows that are sized to fit the content instead of making the flexbox expand to fill its container.  Similaly `show.WRAP` makes a row that packs the data to the left and wraps it (like the way text flows), instead of as spaced-out columns."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a3e7884e",
   "metadata": {},
   "outputs": [],
   "source": [
    "show(show.TIGHT,\n",
    "     [[[show.style(font='italic 24px serif'), 'lots of data',\n",
    "        show.style(background='gainsboro'),\n",
    "     show.WRAP, [f'({a},{b})' for a in range(i) for b in range(j)]]\n",
    "       for j in range(1, 5)] for i in range(1, 3)])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "92a1c75c",
   "metadata": {
    "scrolled": false
   },
   "source": [
    "## Showing widgets\n",
    "\n",
    "baukit also contains a bunch of widgets like `Range()` and `Numberbox()` that can be shown and included in a layout."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "816a2956",
   "metadata": {},
   "outputs": [],
   "source": [
    "from baukit import Range, Numberbox\n",
    "\n",
    "nb = Numberbox()\n",
    "ra = Range()\n",
    "\n",
    "show([[nb, show.style(flex=5), ra]])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "63713d4b",
   "metadata": {},
   "source": [
    "## Getting and setting widget values\n",
    "\n",
    "Widgets are live data-bound objects.  So if you change a value in the widget, it will display that value right away.  Also, if the user enters some input, it will be reflected in the python value right away.  To see that effect, run the following cells while interacting with the widgets above:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8e8a3115",
   "metadata": {},
   "outputs": [],
   "source": [
    "import random\n",
    "ra.value = random.randrange(100)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "74bf1a9c",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(nb.value)\n",
    "print(ra.value)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "770f5858",
   "metadata": {},
   "source": [
    "## Binding live widget properties\n",
    "\n",
    "Widget properties can be bound together - if you refer to a widget property using the `widget.prop()` method, it will return a live property object that can be bound to another property.\n",
    "\n",
    "(Also notice that if you show the same widget instance multiple times, all the rendered views will in sync.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a9c0f78f",
   "metadata": {},
   "outputs": [],
   "source": [
    "nb2 = Numberbox()\n",
    "ra2 = Range(max=1, step=0.001, value=0.5)\n",
    "\n",
    "nb2.value = ra2.prop('value')\n",
    "\n",
    "show([[nb2, show.style(flex=8), ra2]])\n",
    "show(ra2)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4d6cb70a",
   "metadata": {},
   "source": [
    "## Making buttons, and responding to events\n",
    "\n",
    "Widgets fire events when their state changes, and you can have those events call a function using the `on()` method.\n",
    "For example, the following button randomizes the sliders above."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4aa18606",
   "metadata": {},
   "outputs": [],
   "source": [
    "from baukit import Button\n",
    "\n",
    "btn = Button('Click Me')\n",
    "def dosomething():\n",
    "    ra2.value = random.randrange(100) / 100\n",
    "btn.on('click', dosomething)\n",
    "show(show.style(fontSize=20, height=28), btn)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "498e7872",
   "metadata": {},
   "source": [
    "You can also respond to any property change using the `on()` method.  Here we show a message whenever ra3 changes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "834c8ead",
   "metadata": {},
   "outputs": [],
   "source": [
    "from baukit import Div\n",
    "\n",
    "div = Div('change the slider')\n",
    "ra3 = Range()\n",
    "\n",
    "def print_value():\n",
    "    div.innerHTML = f'the value is {ra3.value}'\n",
    "ra3.on('value', print_value)\n",
    "\n",
    "show([[div, show.style(flex=7), ra3]])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dd64396b",
   "metadata": {},
   "source": [
    "## Menus, Choices, and Datalists\n",
    "\n",
    "You can make dropdown menus using `Menu`; radio button choices using `Choice`; and editable dropdowns (combobox) using `Datalist`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dee0f19f",
   "metadata": {},
   "outputs": [],
   "source": [
    "from baukit import Menu\n",
    "\n",
    "menu = Menu(choices=list(range(0,101,10)))\n",
    "ra4 = Range(step=10)\n",
    "menu.value = ra4.prop('value')\n",
    "\n",
    "show([[menu, show.style(flex=7), ra4]])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16e9a0a0",
   "metadata": {},
   "source": [
    "Radio button choice arrays can be arranged horizontally or vertically based on the level of nesting."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c2769d0b",
   "metadata": {},
   "outputs": [],
   "source": [
    "from baukit import Choice\n",
    "\n",
    "choice = Choice(choices=list(range(0,101,50)))\n",
    "ra6 = Range(step=50)\n",
    "choice.value = ra6.prop('value')\n",
    "\n",
    "show([[choice, show.style(flex=6), ra6]])\n",
    "show([[[choice], show.style(flex=6), ra6]])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1fb36e03",
   "metadata": {},
   "source": [
    "A Datalist allows free-form input while also providing a dropdown menu for choices."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "24e6bbf5",
   "metadata": {},
   "outputs": [],
   "source": [
    "from baukit import Datalist\n",
    "dl = Datalist(choices=[0, 40, 60, 100])\n",
    "ra5 = Range(step=1)\n",
    "dl.value = ra5.prop('value')\n",
    "\n",
    "show([[dl, show.style(flex=7), ra5]])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8b8a23a9",
   "metadata": {},
   "source": [
    "## Using PlotWidget\n",
    "\n",
    "Matplotlib plots can be displayed within HTML by showing the matplotlib figure.\n",
    "\n",
    "`PlotWidget` is a widget that manages the matplotlib figure and allows you to create a parameterized plot by writing a plotting function.  Any arguments in your plotting function become parameters of the PlotWidget that you can adjust.\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1146dbc8",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "from baukit import PlotWidget\n",
    "\n",
    "def myplot(plt, frequency=1.0, amplitude=1.0):\n",
    "    [ax] = plt.axes\n",
    "    ax.clear()\n",
    "    ax.set_title(f'Sine wave of frequency {frequency:.2f}, amplitude {amplitude:.2f}')\n",
    "    x = torch.linspace(0, 20, 300)\n",
    "    y = (frequency * x).sin()\n",
    "    ax.plot(x, amplitude * y)\n",
    "    ax.set_ylim(-5, 5)\n",
    "\n",
    "show([[PlotWidget(myplot),\n",
    "       PlotWidget(myplot, frequency=2, amplitude=3)]])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bc9f6a72",
   "metadata": {},
   "source": [
    "## Making interactive matplotlib plots with PlotWidget\n",
    "\n",
    "`PlotWidget` properties can be bound together just like any other widget properties, so it is easy to define an interactive plot interface."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8ed28ab5",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "freq_ra = Range(min=0.1, max=5.0, step=0.01, value=1.0)\n",
    "freq_nb = Numberbox(freq_ra.prop('value'))\n",
    "amp_ra = Range(min=0.1, max=5.0, step=0.01, value=1.0)\n",
    "amp_nb = Numberbox(amp_ra.prop('value'))\n",
    "pw = PlotWidget(myplot, frequency=freq_ra.prop('value'), amplitude=amp_ra.prop('value'), format='svg')\n",
    "show(show.TIGHT, [[pw],\n",
    "                  ['frequency', show.style(flex=10), freq_ra, freq_nb],\n",
    "                  ['amplitude', show.style(flex=10), amp_ra, amp_nb]])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0e1adc74",
   "metadata": {},
   "source": [
    "## Handling PlotWidget clicks\n",
    "\n",
    "When an image or PlotWidget is clicked, you can get the coordinates by listening to the click event."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "439c2809",
   "metadata": {},
   "outputs": [],
   "source": [
    "from baukit import Textbox\n",
    "coord_b = Textbox()\n",
    "def handle_click(e):\n",
    "    loc = e.location\n",
    "    coord_b.value = f'x,y=({loc.x:.2f}, {loc.y:.2f}) inside={loc.inside}'\n",
    "pw.on('click', handle_click)\n",
    "show(show.TIGHT, [['Click the previous plot to see coordinates here:', coord_b]])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e60ab5fa",
   "metadata": {},
   "source": [
    "There are many other widgets that come in the kit.  You can read about them in the source code - see `labwidget.py`."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}